/*******************************************************************************
 * Copyright (c) 2004, 2005 Elias Volanakis and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Elias Volanakis - initial API and implementation
 *******************************************************************************/
package edu.ccut.saturn.studio.logic.model.commands;

import java.util.Iterator;

import org.eclipse.gef.commands.Command;

import edu.ccut.saturn.studio.logic.model.Connection;
import edu.ccut.saturn.studio.logic.model.Shape;


/**
 * A command to reconnect a connection to a different start point or end point.
 * The command can be undone or redone.
 * <p>
 * This command is designed to be used together with a GraphicalNodeEditPolicy.
 * To use this command propertly, following steps are necessary:
 * </p>
 * <ol>
 * <li>Create a subclass of GraphicalNodeEditPolicy.</li>
 * <li>Override the <tt>getReconnectSourceCommand(...)</tt> method.
 * Here you need to obtain the Connection model element from the ReconnectRequest,
 * create a new ConnectionReconnectCommand, set the new connection <i>source</i> by calling
 * the <tt>setNewSource(Shape)</tt> method and return the command instance.
 * <li>Override the <tt>getReconnectTargetCommand(...)</tt> method.</li>
 * Here again you need to obtain the Connection model element from the ReconnectRequest,
 * create a new ConnectionReconnectCommand, set the new connection <i>target</i> by calling
 * the <tt>setNewTarget(Shape)</tt> method and return the command instance.</li>
 * </ol>
 * @see edu.ccut.saturn.studio.logic.parts.ShapeEditPart#createEditPolicies() for an
 * 			 example of the above procedure.
 * @see org.eclipse.gef.editpolicies.GraphicalNodeEditPolicy
 * @see #setNewSource(Shape)
 * @see #setNewTarget(Shape)
 * @author Elias Volanakis
 */
public class ConnectionReconnectCommand extends Command {

private static final String RENAME_COMMAND_MOVE_CONNECTION_ENDPOINT = "move connection endpoint";
private static final String RENAME_COMMAND_MOVE_CONNECTION_STARTPOINT = "move connection startpoint";
private static final String RECONNECTION_COMMAND_SHOULD_NOT_HAPPEN = "Should not happen";
/** The connection instance to reconnect. */
private Connection connection;
/** The new source endpoint. */
private Shape newSource;
/** The new target endpoint. */
private Shape newTarget;
/** The original source endpoint. */
private final Shape oldSource;
/** The original target endpoint. */
private final Shape oldTarget;

/**
 * Instantiate a command that can reconnect a Connection instance to a different source
 * or target endpoint.
 * @param conn the connection instance to reconnect (non-null)
 * @throws IllegalArgumentException if conn is null
 */
public ConnectionReconnectCommand(Connection conn) {
	if (conn == null) {
		throw new IllegalArgumentException();
	}
	this.connection = conn;
	this.oldSource = conn.getSource();
	this.oldTarget = conn.getTarget();
}

/* (non-Javadoc)
 * @see org.eclipse.gef.commands.Command#canExecute()
 */
public boolean canExecute() {
	if (newSource != null) {
		return checkSourceReconnection();
	} else if (newTarget != null) {
		return checkTargetReconnection();
	}
	return false;
}

/**
 * Return true, if reconnecting the connection-instance to newSource is allowed.
 */
private boolean checkSourceReconnection() {
	// connection endpoints must be different Shapes
	if (newSource.equals(oldTarget)) {
		return false;
	}
	// return false, if the connection exists already
	for (Iterator<Connection> iter = newSource.getSourceConnections().iterator(); iter.hasNext();) {
		Connection conn = (Connection) iter.next();
		// return false if a newSource -> oldTarget connection exists already
		// and it is a different instance than the connection-field
		if (conn.getTarget().equals(oldTarget) &&  !conn.equals(connection)) {
			return false;
		}
	}
	return true;
}

/**
 * Return true, if reconnecting the connection-instance to newTarget is allowed. 
 */
private boolean checkTargetReconnection() {
	// connection endpoints must be different Shapes
	if (newTarget.equals(oldSource)) {
		return false;
	}
	// return false, if the connection exists already
	for (Iterator<Connection> iter = newTarget.getTargetConnections().iterator(); iter.hasNext();) {
		Connection conn = (Connection) iter.next();
		// return false if a oldSource -> newTarget connection exists already
		// and it is a differenct instance that the connection-field
		if (conn.getSource().equals(oldSource) && !conn.equals(connection)) {
			return false;
		}
	}
	return true;
}

/**
 * Reconnect the connection to newSource (if setNewSource(...) was invoked before)
 * or newTarget (if setNewTarget(...) was invoked before).
 */
public void execute() {
	if (newSource != null) {
		connection.reconnect(newSource, oldTarget);
		newSource.modifyShape();
		oldSource.modifyShape();
	} else if (newTarget != null) {
		connection.reconnect(oldSource, newTarget);
		
	} else {
		throw new IllegalStateException(RECONNECTION_COMMAND_SHOULD_NOT_HAPPEN);
	}
	
}

/**
 * Set a new source endpoint for this connection.
 * When execute() is invoked, the source endpoint of the connection will be attached
 * to the supplied Shape instance.
 * <p>
 * Note: Calling this method, deactivates reconnection of the <i>target</i> endpoint.
 * A single instance of this command can only reconnect either the source or the target 
 * endpoint.
 * </p>
 * @param connectionSource a non-null Shape instance, to be used as a new source endpoint
 * @throws IllegalArgumentException if connectionSource is null
 */
public void setNewSource(Shape connectionSource) {
	if (connectionSource == null) {
		throw new IllegalArgumentException();
	}
	setLabel(RENAME_COMMAND_MOVE_CONNECTION_STARTPOINT);
	newSource = connectionSource;
	newTarget = null;
}

/**
 * Set a new target endpoint for this connection
 * When execute() is invoked, the target endpoint of the connection will be attached
 * to the supplied Shape instance.
 * <p>
 * Note: Calling this method, deactivates reconnection of the <i>source</i> endpoint.
 * A single instance of this command can only reconnect either the source or the target 
 * endpoint.
 * </p>
 * @param connectionTarget a non-null Shape instance, to be used as a new target endpoint
 * @throws IllegalArgumentException if connectionTarget is null
 */
public void setNewTarget(Shape connectionTarget) {
	if (connectionTarget == null) {
		throw new IllegalArgumentException();
	}
	setLabel(RENAME_COMMAND_MOVE_CONNECTION_ENDPOINT);
	newSource = null;
	newTarget = connectionTarget;
}

/**
 * Reconnect the connection to its original source and target endpoints.
 */
public void undo() {
	connection.reconnect(oldSource, oldTarget);
}
	
}
